/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Copyright 2002-2003 by Mark Wolgemuth, All rights reserved.             */
/* Copyright 2002-2003 by Employease, Inc.  All rights reserved.           */
/*                                                                         */
/* Redistribution and use in source and binary forms, with or without      */
/* modification, are permitted provided that the following conditions      */
/* are met:                                                                */
/*                                                                         */
/*    1. Redistributions of source code must retain the above copyright    */
/* notice, this list of conditions and the following disclaimer.           */
/*    2. Redistributions in binary form must reproduce the above copyright */
/* notice, this list of conditions and the following disclaimer in the     */
/* documentation and/or other materials provided with the distribution.    */
/*                                                                         */
/* THIS SOFTWARE IS PROVIDED BY THE MOD_ATHENA PROJECT ``AS IS'' AND ANY   */
/* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE       */
/* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR      */
/* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR        */
/* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,   */
/* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,     */
/* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR      */
/* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY     */
/* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT            */
/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   */
/* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.    */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <ath_list.h>

/* private protos */
static void   _ath_list_add_node(ath_list *pl, void *p_data, 
				 ath_node *p_node, ath_node **ctx);
static int    _ath_list_ap_add_r(apr_pool_t *pool, ath_list *pl, 
				 void *p_data, ath_node **plctx);
static void * _ath_list_getnext_r(ath_list *pl, ath_node **ctx);
static void   _ath_list_remove_cur_r(ath_list *pl, ath_node **ctx);
static void   _ath_list_remove_prev_r(ath_list *pl, ath_node **ctx);
static void   _ath_list_debug_lock_msg(ath_list *pl, const char *msg);


apr_thread_mutex_t *_debug_this_mutex = NULL;

/* end privates */


/* initializes list / assert occurs here */
void 
ath_list_init(ath_list *pl)
{
    ASSERT(pl);
    ath_list_init_r(pl, &pl->ctx);
}
void ath_list_init_r(ath_list *pl, ath_node **ctx)
{
    ath_node *p_node;
    ASSERT(pl);
    ASSERT(ctx);
    ath_list_lock(pl);
    /* init the obj global ctx ptr */
    if (ctx) 
	(*ctx) = NULL;
    pl->ctx = NULL;
    pl->ll_size = 0;
    ath_list_unlock(pl);
}

/* free the list object */
/* 2005 05 03 mw forcing pool use now
/* void  */
/* ath_list_free(ath_list *pl) */
/* { */
/*     ASSERT(pl); */
/*     ath_list_free_r(pl, &pl->ctx); */
/* } */
/* void  */
/* ath_list_free_r(ath_list *pl, ath_node **ctx) */
/* { */
/*     ath_node *p_node; */
/*     ASSERT(pl); */
/*     ath_list_lock(pl); */
/*     /* free the referenced data and nodes */
/*     while (*ctx) */
/*       { */
/* 	  p_node = (*ctx)->p_next; */
/* 	  if ((*ctx)->p_data) */
/* 	      free((*ctx)->p_data); */
/* 	  free((*ctx)); */
/* 	  (*ctx) = p_node; */
/*       } */
/* #ifdef THREAD_SAFE */
/*     pthread_mutex_destroy(pl->mutex); */
/* #endif */
/*     free(pl); */
/* } */


/* add a data object to list */
/* OBSOLETE */
/* int  */
/* ath_list_add(ath_list *pl, void *p_data) */
/* { */
/*     return ath_list_add_r(pl, p_data, &pl->ctx); */
/* } */
/* int  */
/* ath_list_add_r(ath_list *pl, void *p_data, ath_node **ctx) */
/* { */
/*     int i_retval = 0; */
/*     ath_node *p_node = NULL; */
/*     ASSERT(pl); */
/*     ath_list_lock(pl); */
/*     if (p_node = (ath_node *) malloc(sizeof(ath_node))) */
/*       { */
/* 	  _ath_list_add_node(pl, p_data, p_node, ctx); */
/* 	  i_retval = 1; */
/*       } */
/*     ath_list_unlock(pl); */
/*     return i_retval; */
/* } */

/* return number of nodes in list */
apr_int64_t 
ath_list_size(ath_list *pl)
{
    apr_int64_t size;
    ASSERT(pl);
    ath_list_lock(pl);
    size = pl->ll_size;
    ath_list_unlock(pl);
    return size;
}

/* delete node marked as current in list */
int 
ath_list_delete_cur(ath_list *pl)
{
    return ath_list_delete_cur_r(pl, &pl->ctx);
}
int 
ath_list_delete_cur_r(ath_list *pl, ath_node **ctx)
{
    /* do NOT use */
    ASSERT(0);
/*     ath_node *p_node; */
/*     ASSERT(pl); */
/*     p_node = (*ctx); */
/*     ath_list_remove_cur_r(pl, ctx); */
/*     if (p_node->p_data) */
/* 	free(p_node->p_data); */
/*     free(p_node); */
/*     /* there is no error condition yet */
/*     return 1; */
}

/* delete without freeing */
void 
ath_list_remove_cur(ath_list *pl)
{
    ath_list_remove_cur_r(pl, &pl->ctx);
}
void 
ath_list_remove_cur_r(ath_list *pl, ath_node **ctx)
{
    ASSERT(pl->ll_size > 0);
    ASSERT(*ctx);
    ath_list_lock(pl);
    _ath_list_remove_cur_r(pl, ctx);
    ath_list_unlock(pl);
}

void 
ath_list_remove_prev(ath_list *pl)
{
    ath_list_remove_prev_r(pl, &pl->ctx);
}
void 
ath_list_remove_prev_r(ath_list *pl, ath_node **ctx)
{
    ath_node *p_node;
    ASSERT(pl);
    ASSERT(*ctx);
    ath_list_lock(pl);
    _ath_list_remove_prev_r(pl, ctx);
    ath_list_unlock(pl);
}

/* return the node linked after current node in list
 * may be self (size = 1)
 * may be previous (size = 2, current = tail)
 */
void * 
ath_list_getnext(ath_list *pl)
{
    return ath_list_getnext_r(pl, &pl->ctx);
}
void * 
ath_list_getnext_r(ath_list *pl, ath_node **ctx)
{
    void *p_data = NULL;
    ASSERT(pl);
    ath_list_lock(pl);
    /* allow check on empty array */
    p_data = _ath_list_getnext_r(pl, ctx);
    ath_list_unlock(pl);
    return p_data;
}


/* moves back to head */
void 
ath_list_rewind(ath_list *pl)
{
    ath_list_lock(pl);
    ath_list_rewind_r(pl, &pl->ctx);
    ath_list_unlock(pl);
}
void 
ath_list_rewind_r(ath_list *pl, ath_node **ctx)
{
    ASSERT(pl);
    (*ctx) = NULL;
}

int 
ath_list_hasnext(ath_list *pl)
{
    return ath_list_hasnext_r(pl, &pl->ctx);
} 
int 
ath_list_hasnext_r(ath_list *pl, ath_node **ctx)
{
    int rv;
    ASSERT(pl);
    ath_list_lock(pl);
    if ((*ctx != NULL)
	&& ((*ctx) != pl->p_head))
	rv = 2;
    else if ((*ctx == NULL) && (pl->p_head))
	rv = 1;
    else
	rv = 0;
    ath_list_unlock(pl);
    return rv;
}

void *
ath_list_popnext(ath_list *pl)
{
    return ath_list_popnext_r(pl, &pl->ctx);
} 
void *
ath_list_popnext_r(ath_list *pl, ath_node **ctx)
{
    int rv;
    void *p_data = NULL;
    ASSERT(pl);
    ASSERT(pl->p_head);
    ath_list_lock(pl);
    p_data = _ath_list_getnext_r(pl, ctx);
    _ath_list_remove_prev_r(pl, ctx);
    ath_list_unlock(pl);
    return p_data;
}
int 
ath_list_isempty(ath_list *pl)
{
    int rv;
    ASSERT(pl);
    ath_list_lock(pl);
    if (pl->ll_size > 0)
	rv = 0;
    else
	rv = 1;
    ath_list_unlock(pl);
    return rv;
}

int 
ath_list_at_head(ath_list *pl)
{
   return ath_list_at_head_r(pl, &pl->ctx);
}
int 
ath_list_at_head_r(ath_list *pl, ath_node **ctx)
{
    int rv = 0;
    ASSERT(pl);
    
    ath_list_lock(pl);
    if (!pl->p_head) /* we're empty, so yes */
	rv = 1;
    else if (!(*ctx)) /* haven't positioned yet, so yes */
	rv = 1;
    else if (((*ctx)->p_prev) == pl->p_head)
	rv = 1;
    ath_list_unlock(pl);
    return rv;
}

void *
ath_list_idx(ath_list *pl, int idx)
{
    void *p_data;
    ath_node *p_node;
    ath_list_lock(pl);
    ASSERT(idx < pl->ll_size); /* idx out of bounds */
    if (!pl->p_head)
	p_data = NULL;
    else
      {
	  p_node = pl->p_head;
	  for (; idx > 0; idx--)
	      p_node = p_node->p_next;
	  p_data = p_node->p_data;
      }
    ath_list_unlock(pl);
    return p_data;
}

int
ath_list_lock(ath_list *pl)
{
#ifdef THREAD_SAFE
    ASSERT(pl->mutex);
   if ((_debug_this_mutex)
	&& (_debug_this_mutex == pl->mutex))
       _ath_list_debug_lock_msg(pl, "lock");
    return apr_thread_mutex_lock(pl->mutex);
#else
    return 0;
#endif
}

int
ath_list_unlock(ath_list *pl)
{
#ifdef THREAD_SAFE
    ASSERT(pl->mutex);
   if ((_debug_this_mutex)
	&& (_debug_this_mutex == pl->mutex))
       _ath_list_debug_lock_msg(pl, "unlock");
    return apr_thread_mutex_unlock(pl->mutex);
#else
    return 0;
#endif
}

void 
ath_list_debug_lock(ath_list *pl)
{
#ifdef THREAD_SAFE
    fprintf(stdout, "DEBUG lock: set debug: [%d]\n", pl->mutex);
    _debug_this_mutex = pl->mutex;
#endif
}


/* allocs a list and initializes it */
ath_list * 
ath_list_ap_alloc(apr_pool_t *pool)
{
    apr_status_t result;
    ath_list *pl = NULL;
    pl = (ath_list *) apr_pcalloc(pool, sizeof(ath_list));
    /* call out to init so that one can init */
    result = apr_thread_mutex_create(&pl->mutex, APR_THREAD_MUTEX_DEFAULT, pool);
    ASSERT(result == APR_SUCCESS);
    ASSERT(pl->mutex);
    pl->p_head = NULL;
    pl->ctx = NULL;
    ath_list_init_r(pl, &pl->ctx);
    return pl;
}
ath_list * 
ath_list_ap_alloc_r(apr_pool_t *pool, ath_node **ctx)
{
    apr_status_t result;
    ath_list *pl = NULL;
    pl = (ath_list *) apr_pcalloc(pool, sizeof(ath_list));
    /* call out to init so that one can init */
    result = apr_thread_mutex_create(&pl->mutex, APR_THREAD_MUTEX_DEFAULT, pool);
    ASSERT(result == APR_SUCCESS);
    ASSERT(pl->mutex);
    /* only used if _r is not called after init */
    pl->p_head = NULL;
    pl->ctx = NULL;
    ath_list_init_r(pl, ctx);
    return pl;
}

/* add a data object to list */
int 
ath_list_ap_add(apr_pool_t *pool, ath_list *pl, 
		void *p_data)
{
    return ath_list_ap_add_r(pool, pl, p_data, &pl->ctx);
}
int 
ath_list_ap_add_r(apr_pool_t *pool, ath_list *pl, 
		  void *p_data, ath_node **plctx)
{
    int i_retval = 0;
    ath_node *p_node = NULL;
    ASSERT(pl);
    ath_list_lock(pl);
    _ath_list_ap_add_r(pool, pl, p_data, plctx);
    ath_list_unlock(pl);
    return i_retval;
}

int
ath_list_ap_add_tail(apr_pool_t *pool, ath_list *pl, void *p_data)
{
    ath_node *ctx;
    ath_list_lock(pl);
    if (!pl->p_head)
	ctx = NULL;
    else
	ctx = pl->p_head->p_prev;
    _ath_list_ap_add_r(pool, pl, p_data, &ctx);
    ath_list_unlock(pl);
    //fprintf(stdout, "[%s:%d] list: %d tail added: %d\n", __FILE__, __LINE__, pl, p_data);
    return 0;
}

/* privates */

/* only used by ath_list_ap_add */
/* thread safety handled in adds */
void 
_ath_list_add_node(ath_list *pl, void *p_data, 
		  ath_node *p_node, ath_node **ctx)
{
    ASSERT(pl);
    ASSERT(p_node);
    /* storing null is allowed for null, although it is confusing */
    p_node->p_data = p_data;
    if (pl->p_head != NULL) /* already has els */
      {
	  if (!(*ctx))
	      (*ctx) = pl->p_head;
	  p_node->p_next = (*ctx);
	  p_node->p_prev = (*ctx)->p_prev;
	  (*ctx)->p_prev->p_next = p_node;
	  (*ctx)->p_prev = p_node;
      } 
    else /* empty */
      {
	  (*ctx) = p_node;
	  pl->p_head = p_node;
	  (*ctx)->p_next = p_node;
	  (*ctx)->p_prev = p_node;
      }
    pl->ll_size++;
}

static int 
_ath_list_ap_add_r(apr_pool_t *pool, ath_list *pl, 
		  void *p_data, ath_node **plctx)
{
    int i_retval = 0;
    ath_node *p_node = NULL;
    ASSERT(pl);
    if (p_node = (ath_node *) 
	apr_pcalloc(pool, (sizeof(ath_node))))
      {
	  _ath_list_add_node(pl, p_data, p_node, plctx);
	  i_retval = 1;
      }
    return i_retval;
}

static void *
_ath_list_getnext_r(ath_list *pl, ath_node **ctx)
{
    ath_node *p_node;
    void *p_data = NULL;
    if (pl->p_head)
      {
	  if (!(*ctx))
	      (*ctx) = pl->p_head;
	  p_node = (*ctx);
	  (*ctx) = p_node->p_next;
	  p_data = p_node->p_data;
      }
    return p_data;
}

static void 
_ath_list_remove_cur_r(ath_list *pl, ath_node **ctx)
{
    ath_node *p_node;
    ASSERT(pl->ll_size > 0);
    ASSERT(*ctx);
    if ((*ctx) != (*ctx)->p_next)
      {
	  (*ctx)->p_next->p_prev = (*ctx)->p_prev;
	  (*ctx)->p_prev->p_next = (*ctx)->p_next;
	  if (pl->p_head == (*ctx))
	    {
		pl->p_head = (*ctx)->p_next;
		/* reposition */
		p_node = NULL;
	    }
	  else
	      p_node = (*ctx)->p_next;
	  (*ctx) = p_node;
      }
    else
      {
	  // empty now
	  (*ctx) = NULL;
	  pl->p_head = NULL;
      }
    pl->ll_size--;
}

static void 
_ath_list_remove_prev_r(ath_list *pl, ath_node **ctx)
{
    ath_node *p_node;
    ASSERT(pl);
    ASSERT(*ctx);
    p_node = (*ctx);
    (*ctx) = p_node->p_prev;
    _ath_list_remove_cur_r(pl, ctx);
}

static void
_ath_list_debug_lock_msg(ath_list *pl, const char *msg)
{
#ifdef THREAD_SAFE
    fprintf(stdout, "DEBUG lock: mutex matches: (%d)->(%d) (%s)\n", 
	    pl, pl->mutex, msg);
#endif
}
